package scales.xml.serializers
import java.io.Writer
import java.nio.charset.Charset
import scales.xml._
import impl._
import scales.utils._
import resources._
import javax.xml.parsers._
trait LSSerializerFactoryBase extends SerializerFactory {
import org.w3c.dom._
import ls._
type ExactSerializer = serializers.LSSerializer
def createSerializer( sdata: SerializerData, dbf : DocumentBuilderFactory) : serializers.LSSerializer = {
val db = dbf.newDocumentBuilder
val ndoc = db.newDocument()
ndoc.setXmlVersion(sdata.version.version)
new serializers.LSSerializer {
val docBuilderF = dbf
val data = sdata
val encMap = encF(sdata.encoding)
lazy val doc = ndoc
lazy val impl = doc.getImplementation().asInstanceOf[DOMImplementationLS]
lazy val lsout = impl.createLSOutput()
lazy val lsaout = impl.createLSOutput()
lazy val lss = impl.createLSSerializer()
}
}
val encF: Charset => String => Option[Throwable]
def borrow( sdata : SerializerData ) : ExactSerializer = {
val dbf = DefaultDOMFactoryPool.grab
val s = createSerializer(sdata, dbf)
s.lsout.setEncoding(sdata.encoding.displayName)
s.lsout.setCharacterStream(sdata.out)
s.lsaout.setEncoding(sdata.encoding.displayName)
val dc = s.lss.getDomConfig
dc.setParameter("split-cdata-sections", false)
dc.setParameter("well-formed", true)
dc.setParameter("xml-declaration", false)
s
}
def giveBack( serializer : ExactSerializer ) {
DefaultDOMFactoryPool.giveBack( serializer.docBuilderF )
}
def apply[R](thunk: Serializer => R)(sdata: SerializerData): R = {
val s = borrow(sdata)
try {
thunk(s)
} finally {
giveBack(s)
}
}
}
object LSSerializerFactoryXHTML extends LSSerializerConcurrentCacheFactoryXHTML {
val xhtmlNS = "http://www.w3.org/1999/xhtml"
val canBeEmpty = Set( "area", "base", "br", "col", "hr",
"img", "input", "link", "meta", "param" )
}
trait LSSerializerConcurrentCacheFactoryXHTML extends LSSerializerConcurrentCacheFactory {
import org.w3c.dom._
import ls._
override def createSerializer( sdata: SerializerData, dbf : DocumentBuilderFactory) : serializers.LSSerializer = {
val db = dbf.newDocumentBuilder
val ndoc = db.newDocument()
ndoc.setXmlVersion(sdata.version.version)
new serializers.XHTMLLSSerializer {
val docBuilderF = dbf
val data = sdata
val encMap = encF(sdata.encoding)
lazy val doc = ndoc
lazy val impl = doc.getImplementation().asInstanceOf[DOMImplementationLS]
lazy val lsout = impl.createLSOutput()
lazy val lsaout = impl.createLSOutput()
lazy val lss = impl.createLSSerializer()
}
}
}
object LSSerializerFactory extends LSSerializerConcurrentCacheFactory {
}
trait LSSerializerConcurrentCacheFactory extends LSSerializerFactoryBase {
import java.util.concurrent.ConcurrentHashMap
import scales.utils.collection.Once
val globalEncMap = new ConcurrentHashMap[String, ConcurrentHashMap[Charset, Once[Option[Throwable]]]]
val encF = { encoding: Charset =>
val encoder = encoding.newEncoder
if (encoding.contains(defaultCharset)) { s: String => None }
else { s: String =>
calcOnce(encoding, valueOf(s, globalEncMap)(new ConcurrentHashMap[Charset, Once[Option[Throwable]]]())) {
if (encoder.canEncode(s))
None
else
Some(InvalidCharacterInMarkup(s))
}
}
}
}
object LSSerializerNoCacheFactory extends LSSerializerNoCacheFactoryT {
}
trait LSSerializerNoCacheFactoryT extends LSSerializerFactoryBase {
import java.util.HashMap
val encF = { encoding: Charset =>
val encoder = encoding.newEncoder
val map = new HashMap[String, Option[Throwable]]
if (encoding.contains(defaultCharset)) { s: String => None }
else { s: String =>
var r = map.get(s)
if (r eq null) {
r = if (encoder.canEncode(s))
None
else
Some(InvalidCharacterInMarkup(s))
map.put(s, r)
}
r
}
}
}
trait XHTMLLSSerializer extends LSSerializer {
import data._
override def emptyElement(qName: QName, attributes: Traversable[Attribute], namespaces: Map[String, String], declareDefaultNS: Option[String], path: List[QName]): Option[Throwable] =
doElem(qName, attributes, namespaces, declareDefaultNS) orElse {
val canBeEmpty =
if (qName.namespace.uri == LSSerializerFactoryXHTML.xhtmlNS)
if (LSSerializerFactoryXHTML.canBeEmpty(qName.local))
true
else
false
else
true
if (canBeEmpty) {
out.append(" />")
None
} else {
out.append(">")
endElement(qName, path)
}
}
}
trait LSSerializer extends Serializer {
import javax.xml.parsers._
import org.w3c.dom._
import ls._
import java.util.concurrent.ConcurrentHashMap
val data: SerializerData
import data._
val docBuilderF: DocumentBuilderFactory
val doc: Document
val impl: DOMImplementationLS
val lsout: LSOutput
val lss: ls.LSSerializer
val lsaout: LSOutput
val encMap: String => Option[Throwable]
lazy val textNode = doc.createTextNode("")
lazy val encoder = encoding.newEncoder()
lazy val canEncode =
if (encoding.contains(defaultCharset)) { s: String => true }
else { s: String => encoder.canEncode(s) }
def ct(t: => Boolean, s: => String): Option[Throwable] = {
try {
if (t)
None
else
Some(CannotSerialize(s))
} catch {
case t: Throwable => Some(t)
}
}
def writeNonText(item: XmlItem, path: List[QName]): Option[Throwable] =
SerializerHelpers.item(out, item, path)
def item(item: XmlItem, path: List[QName]): Option[Throwable] = {
val canEncodeI: Option[Throwable] = item match {
case x: scales.xml.Text =>
None
case x: CData =>
if (canEncode(item.value))
None
else
Some(CDataCannotBeEncoded(item.value))
case x: scales.xml.Comment =>
if (canEncode(item.value))
None
else
Some(CommentCannotBeEncoded(item.value))
case x: scales.xml.PI =>
if (canEncode(x.value) && canEncode(x.target))
None
else
Some(PICannotBeEncoded("Target: " + x.target + " value: " + x.value))
}
canEncodeI orElse {
item match {
case x: scales.xml.Text =>
textNode.setNodeValue(item.value)
ct(lss.write(textNode, lsout),
{
val str = new java.io.StringWriter();
SerializerHelpers.item(str, item, path)
str.toString
})
case _ =>
writeNonText(item, path)
}
}
}
def writeAttr(before: => String, toCt: => String, after: => String) =
ct({
out.append(before)
textNode.setNodeValue(toCt)
val str = new java.io.StringWriter();
lsaout.setCharacterStream(str)
val r = lss.write(textNode, lsaout)
lsaout.setCharacterStream(null)
if (r) {
out.append(str.toString.replaceAll("\"", """))
out.append(after)
}
r
}, before + toCt + after)
def doElem(qName: QName, attribs: Traversable[Attribute], ns: Map[String, String], declareDefaultNS: Option[String]): Option[Throwable] = {
{
if (qName.qNameVersion == Xml11 && version == Xml10)
Some(IncompatibleQNameVersions(qName.qName))
else
None
} orElse {
encMap(qName.qName)
} orElse {
out.append("<" + qName.qName)
None
} orElse {
declareDefaultNS.flatMap { dns =>
writeAttr(" xmlns=\"", dns, "\"")
}
} orElse {
ns.foldLeft(None: Option[Throwable]) { (r, x) =>
r orElse {
if (QNameCharUtils.validXmlPrefix(x._1)(version) &&
QNameCharUtils.validXmlNamespace(x._2)(version)) {
out.append(" xmlns:")
encMap(x._1) orElse
writeAttr(x._1 + "=\"", x._2, "\"")
} else
Some(IncompatibleQNameVersions("NS:"+x._1+"->"+x._2))
}
}
} orElse {
import ScalesXml._
attribs.foldLeft(None: Option[Throwable]) { (r, x) =>
r orElse {
val name = x.name
if (name.qNameVersion == Xml11 && version == Xml10)
Some(IncompatibleQNameVersions("Attr:"+name.qName))
else {
val n = name.qName
encMap(n) orElse
writeAttr(" " + n + "=\"", x.value, "\"")
}
}
}
}
}
def emptyElement(qName: QName, attributes: Traversable[Attribute], namespaces: Map[String, String], declareDefaultNS: Option[String], path: List[QName]): Option[Throwable] =
doElem(qName, attributes, namespaces, declareDefaultNS) orElse {
out.append("/>")
None
}
def startElement(qName: QName, attributes: Traversable[Attribute], namespaces: Map[String, String], declareDefaultNS: Option[String], path: List[QName]): Option[Throwable] =
doElem(qName, attributes, namespaces, declareDefaultNS) orElse {
out.append(">")
None
}
def endElement(qName: QName, path: List[QName]): Option[Throwable] = {
out.append("</" + qName.qName + ">")
None
}
def xmlDeclaration(encoding: Charset, version: XmlVersion): Option[Throwable] = SerializerHelpers.xmlDecl(out, encoding, version)
}